package work.mediway.mdm.controller.common;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.mediway.hos.app.base.seure.util.SecurityUtils;
import com.mediway.hos.base.model.BaseResponse;
import com.mediway.hos.log.annotation.OperLog;
import com.mediway.hos.messgateway.model.entity.MessageType;
import com.mediway.hos.messgateway.service.MessageTypeService;

import org.apache.poi.ss.usermodel.Font;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import work.mediway.mdm.core.helper.BaseDatabaseHelper;
import work.mediway.mdm.core.page.QueryRequest;
import work.mediway.mdm.core.annotation.OpsLog;
import work.mediway.mdm.core.constant.MdmConstant;
import work.mediway.mdm.core.enums.CodeSystemEnum;
import work.mediway.mdm.core.enums.OpsLogTypeEnum;
import work.mediway.mdm.core.enums.StatusEnum;
import work.mediway.mdm.core.enums.TablePrefixEnum;
import work.mediway.mdm.core.transport.ErrorMsgVO;
import work.mediway.mdm.core.util.DUtil;
import work.mediway.mdm.core.util.HttpContextUtil;
import work.mediway.mdm.biz.dto.dict.CodeSystemDTO;
import work.mediway.mdm.biz.dto.dict.DictDTO;
import work.mediway.mdm.biz.dto.dict.DictDetailDTO;
import work.mediway.mdm.biz.dto.dict.DictFieldDTO;
import work.mediway.mdm.biz.dto.dict.DictGeneralDTO;
import work.mediway.mdm.biz.dto.dict.DictListDTO;
import work.mediway.mdm.biz.dto.dict.DictStatisticsDTO;
import work.mediway.mdm.biz.entity.dict.CtBusinessField;
import work.mediway.mdm.biz.service.biz.TableFieldService;
import work.mediway.mdm.biz.service.common.CommonDictService;
import work.mediway.mdm.biz.service.dict.CtBusinessFieldService;
import work.mediway.mdm.biz.service.dict.CtCodeSystemService;
import work.mediway.mdm.biz.vo.dict.BusinessFieldVO;
import work.mediway.mdm.biz.vo.dict.DictFieldVO;
import work.mediway.mdm.biz.vo.dict.DictListVO;
import work.mediway.mdm.biz.vo.dict.DictStatisticsVO;

/**
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2020/11/27
 */
@SuppressWarnings("unchecked")
@Api(tags = "字典数据模块")
@ApiSupport(order = 5)
@Validated
@RequestMapping("dict")
@RestController
@RequiredArgsConstructor
public class CommonDictController {

    private final CtCodeSystemService codeSystemService;
    private final CommonDictService commonDictService;
    private final CtBusinessFieldService businessFieldService;
    private final BaseDatabaseHelper baseDatabaseHelper;
    private final MessageTypeService messageTypeService;
    private final TableFieldService tableFieldService;


    @OpsLog(value = "查询字典表中代码描述", type = OpsLogTypeEnum.SELECT)
    @GetMapping
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "查询字典表中代码描述")
    @ApiImplicitParam(value = "是否树形展示(默认为false)", name = "tree", paramType = "query", allowableValues = "false,true")
    public BaseResponse<Object> listDict(@Valid DictDTO dictDTO, @RequestParam(defaultValue = "false") boolean tree) {
        Object result = this.commonDictService.listDictCodeAndDesc(dictDTO, tree);
        return ObjectUtil.isNotEmpty(result) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OpsLog(value = "查询字典列表", type = OpsLogTypeEnum.SELECT)
    @PostMapping("list")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "查询字典列表")
    public BaseResponse<Page<DictListVO>> pageDict(@RequestBody DictListDTO dictDTO) {
        Page<DictListVO> result = this.codeSystemService.pageCodeSystem(dictDTO);
        return CollUtil.isNotEmpty(result.getRecords()) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OpsLog(value = "查询字典所有列表", type = OpsLogTypeEnum.SELECT)
    @GetMapping("list/all")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "查询字典所有列表")
    @ApiImplicitParam(value = "是否绑定消息", name = "isBindMsg", paramType = "query")
    public BaseResponse<Page<DictListVO>> pageDictAll(DictListDTO dictDTO, QueryRequest queryRequest, @RequestParam(defaultValue = "false") boolean isBindMsg) {
        Page<DictListVO> result = this.codeSystemService.pageCodeSystemAll(dictDTO, queryRequest);
        if (isBindMsg) {
            List<MessageType> messageTypes = this.messageTypeService.selectAllMessageTypeList(new MessageType());
            List<String> bind = messageTypes.stream().map(MessageType::getTypeName).collect(Collectors.toList());
            List<DictListVO> listVOS = result.getRecords().stream().filter(item -> !CollUtil.contains(bind, item.getCodeSystemCode())).collect(Collectors.toList());
            result.setRecords(listVOS);
            result.setTotal(listVOS.size());
            result.setPages(PageUtil.totalPage(listVOS.size(), Integer.parseInt(StrUtil.toString(result.getSize()))));
            return BaseResponse.success();
        }
        return CollUtil.isNotEmpty(result.getRecords()) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OperLog(title = "字典信息-新增/更新字典列表信息", content = "新增/更新字典列表信息")
    @OpsLog(value = "新增/更新字典列表信息", type = OpsLogTypeEnum.UPDATE)
    @PostMapping("list/update")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "更新字典列表信息")
    public BaseResponse<String> modifyDictTable(CodeSystemDTO codeSystem) {
        this.codeSystemService.modifyDictTable(codeSystem);
        return BaseResponse.success();
    }

    @OpsLog(value = "查询表字段信息", type = OpsLogTypeEnum.SELECT)
    @GetMapping("field/list")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "查询表字段信息")
    @ApiImplicitParam(value = "是否显示表中存在外键关联字段的描述字段", name = "showForeign", paramType = "query", allowableValues = "false,true")
    public BaseResponse<List<DictFieldVO>> listDictField(@Valid DictFieldDTO dictDTO, @RequestParam(defaultValue = "false") boolean showForeign) {
        List<DictFieldVO> result = this.tableFieldService.listDictField(dictDTO, showForeign);
        return CollUtil.isNotEmpty(result) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OpsLog(value = "查询/导出字典数据详情", type = OpsLogTypeEnum.SELECT)
    @GetMapping("detail")
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "查询/导出字典数据详情")
    @ApiImplicitParam(value = "是否导出成.xlsx的Excel文件", name = "export", paramType = "query", allowableValues = "false,true")
    public BaseResponse<Page<Map<String, Object>>> dictDetailShowOrExport(@Valid DictFieldDTO dictDTO, QueryRequest queryRequest, @RequestParam(defaultValue = "false") boolean export) throws IOException {
        // 查询数据
        Page<Map<String, Object>> result = this.commonDictService.dictDetailShowOrExport(dictDTO, queryRequest, export, false);
        if (export) {
            return exportDictData(dictDTO, result);
        }
        return CollUtil.isNotEmpty(result.getRecords()) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OperLog(title = "字典信息-保存/更新字典数据详情", content = "保存/更新字典数据详情")
    @OpsLog(value = "保存/更新字典数据详情", type = OpsLogTypeEnum.INSERT)
    @PostMapping("detail")
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "保存/更新字典数据详情")
    public BaseResponse<ErrorMsgVO> saveDictDetail(@RequestBody List<DictDetailDTO> dictDetails) {
        ErrorMsgVO exceptions = this.commonDictService.saveDictDetail(dictDetails, null);
        return BaseResponse.success(exceptions);
    }

    private BaseResponse<Page<Map<String, Object>>> exportDictData(@Valid DictFieldDTO dictDTO, Page<Map<String, Object>> result) throws IOException {
        HttpServletResponse response = HttpContextUtil.getResponse();
        ServletOutputStream out = response.getOutputStream();
        List<Map<String, String>> data = CollUtil.newArrayList();
        ExcelWriter writer = ExcelUtil.getWriterWithSheet(dictDTO.getTableCode()).autoSizeColumnAll();
        Font font = writer.createFont();
        font.setBold(true);
        // 设置标题字体加粗
        writer.getHeadCellStyle().setFont(font);
        // 查询数据库字段
        List<DictFieldVO> field = this.tableFieldService.listDictField(dictDTO, true);
        // 设置表头别名
        field.forEach(item -> writer.addHeaderAlias(item.getFieldDesc(), item.getFieldDesc()));
        // 组装数据
        for (Map<String, Object> record : result.getRecords()) {
            Map<String, String> row = MapUtil.newHashMap();
            for (DictFieldVO vo : field) {
                String value = String.valueOf(record.get(vo.getFieldCode()));
                if (StrUtil.isBlank(value) || StrUtil.equalsIgnoreCase(value, "null")) {
                    value = "";
                } else {
                    // 日期字段特殊处理
                    if (DUtil.isDate(value)) {
                        value = DateUtil.formatDate(DateUtil.parseDate(value));
                    }
                    if (DUtil.isTime(value)) {
                        value = DateUtil.formatTime(DateUtil.parseTime(value));
                    }
                }
                row.put(vo.getFieldDesc(), value);
            }
            data.add(row);
        }
        // 一次性写出内容，使用默认样式，强制输出标题
        writer.write(data, true);
        DictListDTO listDTO = new DictListDTO();
        listDTO.setDictParam(dictDTO.getTableCode());
        DictListVO codeSystem = this.codeSystemService.pageCodeSystem(listDTO).getRecords().get(0);
        response.setContentType(MdmConstant.REQUEST_HEADER_CONTENT_TYPE);
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        // 这里URLEncoder.encode可以防止中文乱码
        String fileName = codeSystem.getCodeSystemDesc() + StringPool.LEFT_BRACKET + codeSystem.getCodeSystemCode() + StringPool.RIGHT_BRACKET;
        // 查询表信息
        response.setHeader("Content-Disposition", writer.getDisposition(fileName + MdmConstant.VALID_FILE_TYPE[0], CharsetUtil.CHARSET_UTF_8));

        writer.flush(out, true);
        writer.close();
        IoUtil.close(out);
        return null;
    }

    @OperLog(title = "字典信息-导入字典数据", content = "导入字典数据")
    @OpsLog(value = "导入字典数据", type = OpsLogTypeEnum.UPLOAD)
    @PostMapping("upload")
    @ApiOperationSupport(order = 10)
//    @ApiOperation(value = "导入字典数据")
    @ApiImplicitParam(value = "表名称", name = "tableCode", paramType = "query", required = true)
    public BaseResponse<String> uploadDictDetail(MultipartFile file, @NotBlank(message = "表名称不能为空") String tableCode) throws IOException {
        this.commonDictService.uploadDictDetail(file.getInputStream(), tableCode);
        return BaseResponse.success();
    }

    // =========================================== 字典统计信息 ========================================== //

    @OpsLog(value = "获取字典增量信息", type = OpsLogTypeEnum.SELECT)
    @GetMapping("change")
    @ApiOperationSupport(order = 13)
    @ApiOperation(value = "获取字典增量信息")
    public BaseResponse<DictStatisticsVO> dictChange(@Valid DictStatisticsDTO statisticsDTO) {
        DictStatisticsVO vo = this.commonDictService.dictChange(statisticsDTO);
        return BaseResponse.success(vo);
    }

    // =========================================== 业务域 ========================================== //

    @OperLog(title = "字典信息-新增业务域", content = "新增业务域")
    @OpsLog(value = "新增业务域", type = OpsLogTypeEnum.INSERT)
    @PostMapping("business")
    @ApiOperationSupport(order = 15)
    @ApiOperation(value = "新增业务域", notes = "新增时代码code不传,后台自动生成")
    public BaseResponse<String> addBusiness(CtBusinessField businessField) {
        businessField.setCode(this.baseDatabaseHelper.buildCode(TablePrefixEnum.CT_BusinessField));
        businessField.setCodeSystemCode(CodeSystemEnum.CT_BusinessField.getType());
        businessField.setLastUpdateUserId(SecurityUtils.getLoginName());
        businessField.setLastUpdateDate(DUtil.splitNow()[0]);
        businessField.setLastUpdateTime(DUtil.splitNow()[1]);
        this.commonDictService.saveDictDetail(DictGeneralDTO.build(businessField, CtBusinessField.class), null);
        return BaseResponse.success();
    }

    @OpsLog(value = "查询业务域医院列表", type = OpsLogTypeEnum.SELECT)
    @GetMapping("business-hospital")
    @ApiOperationSupport(order = 16)
//    @ApiOperation(value = "查询业务域医院列表")
    public BaseResponse<Page<BusinessFieldVO>> selectBusinessHospital(QueryRequest queryRequest) {
        Page<BusinessFieldVO> result = this.businessFieldService.pageBusinessHospital(queryRequest);
        return CollUtil.isNotEmpty(result.getRecords()) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OpsLog(value = "查询业务域列表", type = OpsLogTypeEnum.SELECT)
    @GetMapping("business")
    @ApiOperationSupport(order = 17)
    @ApiImplicitParam(value = "代码或描述", name = "param", paramType = "query")
    @ApiOperation(value = "查询业务域列表")
    public BaseResponse<Page<BusinessFieldVO>> listBusiness(String param, QueryRequest queryRequest) {
        Page<BusinessFieldVO> result = this.businessFieldService.pageBusiness(param, queryRequest);
        return CollUtil.isNotEmpty(result.getRecords()) ? BaseResponse.success(result) : BaseResponse.error();
    }

    @OperLog(title = "字典信息-更新业务域", content = "更新业务域")
    @OpsLog(value = "更新业务域", type = OpsLogTypeEnum.UPDATE)
    @PostMapping("business/update")
    @ApiOperationSupport(order = 19)
    @ApiOperation(value = "更新业务域", notes = "更新时需要将所有信息一起传入,否则会丢失之前的数据")
    public BaseResponse<String> updateBusiness(@Valid CtBusinessField businessField) {
        CtBusinessField one = this.businessFieldService.getOne(Wrappers.lambdaQuery(CtBusinessField.class).eq(CtBusinessField::getCode, businessField.getCode()));
        if (ObjectUtil.isEmpty(one)) {
            return BaseResponse.error(StrUtil.format("对应代码【code={}】不存在", businessField.getCode()));
        }
        BeanUtil.copyProperties(businessField, one, CopyOptions.create().setIgnoreNullValue(true).setIgnoreError(true));
        one.setCodeSystemCode(CodeSystemEnum.CT_BusinessField.getType());
        String[] date = StrUtil.splitToArray(DateUtil.now(), " ");
        one.setLastUpdateDate(date[0]);
        one.setLastUpdateTime(date[1]);
        this.businessFieldService.updateById(one);
        return BaseResponse.success();
    }

    @OperLog(title = "字典信息-删除业务域记录", content = "删除业务域记录")
    @OpsLog(value = "删除业务域记录", type = OpsLogTypeEnum.DELETE)
    @PostMapping("business/delete")
    @ApiOperationSupport(order = 21)
    @ApiOperation(value = "删除业务域记录")
    @ApiImplicitParam(value = "代码值(多个之间使用|分隔)", name = "codes", paramType = "query")
    public BaseResponse<String> deleteBusiness(String codes) {
        for (String code : StrUtil.splitTrim(codes, StringPool.PIPE)) {
            CtBusinessField one = this.businessFieldService.getOne(Wrappers
                    .lambdaQuery(CtBusinessField.class).eq(CtBusinessField::getCode, code));
            if (ObjectUtil.isNotEmpty(one)) {
                one.setStatus(StatusEnum.STATUS_DELETE.getStatusCode());
                this.businessFieldService.updateById(one);
            }
        }
        return BaseResponse.success();
    }

}